17. React Router

Wyzwania:

  • nauczysz się dodawać nowe widoki do swojej aplikacji,
  • dodasz animowane przejścia między widokami,
  • stworzysz nową, rozbudowaną aplikację.

17.1. Single Page Application

W poprzednich dwóch modułach rozpoczęliśmy naszą przygodę z Reactem i Reduksem. Dotychczasowa wiedza pozwoliła nam na stworzenie całkiem rozbudowanej aplikacji to-do, z opcją filtrowania kart oraz z zaimplementowanym reduksowym modelem kontroli stanu aplikacji. Cały nasz projekt jednakże składał się tylko z jednej strony. A co, gdybyśmy chcieli dodać do niego kolejny widok, na przykład stronę "O mnie"?

Jak pamiętasz z wcześniejszych modułów, w klasycznym modelu każda podstrona to osobny plik .html, a przejście z jednej podstrony na drugą wymaga przeładowania całej strony. Wiesz już również, że to rozwiązanie nie jest komfortowe dla użytkownika, gdyż pociąga ono za sobą konieczność oczekiwania, aż serwer odpowie na zapytanie i odeśle nową treść. Pracując nad naszym pierwszym większym projektem, czyli nad blogiem, obeszliśmy ten problem, pokazując i ukrywając za pomocą JavaScriptu treści zawarte w kodzie HTML naszej strony. Następnie, rozwijając projekt pizzerii, poszliśmy o krok dalej, używając AJAX-a do pobierania danych, które miały być wyświetlone na stronie, oraz do zmiany adresu URL (np. #/booking czy #/order).

Zarówno blog, jak i strona pizzerii były w istocie prostymi przykładami Single Page Application (SPA). W ciągu ostatnich lat tego typu aplikacje zyskały wielką popularność ze względu na wygodę użytkowania i szybkość działania. Strony napisane w technologii SPA zawierają tylko jeden plik HTML, zamiast – jak w klasycznym modelu – osobnego pliku dla każdej podstrony. Ten plik jest uruchamiany podczas wchodzenia na stronę główną, a jego podstawowym zadaniem jest załadowanie potrzebnego skryptu (lub skryptów) JS, w których zawarta jest logika całej aplikacji.

W projekcie pizzerii, stworzonym w waniliowym JavaScripcie, całą tę logikę i obsługę przejść między "podstronami" musieliśmy napisać ręcznie. Teraz jednak, mając na podorędziu Reacta, znacznie ułatwimy sobie zadanie – tak jak w przypadku Reduksa, liczne problemy, na które moglibyśmy się natknąć, zostały już rozwiązane, a gotowe pakiety tylko czekają, aż je zainstalujemy i wdrożymy w naszym projekcie!

Reactowe SPA

Podstawowym pytaniem, które musimy sobie zadać, planując reactową aplikację typu SPA, to: jak określić, który komponent powinien być wyświetlany na danej podstronie? Odpowiedzią na to pytanie jest routing: można go określić jako wewnętrzny adres naszej aplikacji, którego głównym zadaniem jest zarządzanie stanem komponentów.

To on decyduje, czy dany komponent powinien być aktywny, czy też nie. Innymi słowy, routing jest odpowiedzialny za to, by nasza aplikacja wiedziała, że na przykład stronę "O mnie" powinna wyświetlić, gdy URL kończy się ciągiem znaków /about, oraz za to, by na tej podstronie pojawiły się odpowiednie komponenty.

Oczywiście, routing robi dużo więcej, ale w tym momencie nie musisz zawracać sobie tym głowy. Konkretne przypadki omówimy sobie w praktyce, gdy będziemy dodawać go do naszego projektu.

Do dzieła!

Zadanie: Dodajemy Container

W naszej aplikacji, jak do tej pory, brakowało kontenera, który ograniczałby szerokość elementów wyświetlanych na stronie. Czas to rozwiązać!

W src/components dodaj folder Container, a w nim pliki Container.scss oraz Container.js. Zawartość pierwszego z nich będzie następująca:

.component {
  width: 90%;
  max-width: 1400px;
  margin: 0 auto;
}

Drugi z nich musisz stworzyć samodzielnie. Ma to być komponent funkcjonalny, renderujący diva o klasie {styles.component}, w którym znajdzie się props children.

Wykorzystaj Container w komponentach Search oraz List, opakowując w niego główny element w funkcji render.

17.2. Podstawy React Router

Zaplanowanie prac

Podstawowy reactowy routing przećwiczymy sobie na naszej aplikacji to-do. Zastanówmy się teraz, jakie funkcjonalności chcemy do niej dodać.

Załóżmy, że oprócz widoku wyświetlającego kolumny z kartami, chcemy mieć dodatkową stronę ze statyczną treścią, zawierającą informacje o naszej aplikacji. Ta strona powinna pokazać się, gdy do adresu w pasku przeglądarki dodamy frazę /info.

Co więcej, chcemy, aby po kliknięciu na którąkolwiek kolumnę, jej zawartość otworzyła się w nowym widoku. Tu już sprawa będzie nieco bardziej złożona, bo kolumny generowane są dynamicznie, więc nie możemy przewidzieć, jakie będą ich adresy.

Ponadto, aby ułatwić poruszanie się po stronie, dodamy do naszej aplikacji pasek nawigacji z logo i linkami do statycznych stron:

image

Jak widzisz, aktywny link w nawigacji jest podświetlony, więc nasza aplikacja będzie musiała wiedzieć, na jakiej podstronie znajduje się użytkownik, i powiązać tę stronę z konkretnym odnośnikiem w menu. Spokojnie jednak, omówimy sobie wszystko, kiedy przyjdzie na to czas. Powyższe zadania będziemy wykonywać w następującej kolejności:

  • dodanie strony statycznej i wyświetlenie jej pod adresem /info,
  • obsługa nawigacji i podświetlanie aktywnych linków,
  • dodanie możliwości kliknięcia kolumny, co otworzy jej zawartość w nowym widoku i zmieni adres na localhost/column/tytuł_kolumny, np. localhost:8080/column/books.

Instalujemy React Router

Pakietem, który ułatwi nam zarządzanie routingiem w aplikacjach reactowych, będzie popularny React Router. Zainstaluj React Router w katalogu swojego projektu za pomocą poniższej komendy:

npm install -S react-router-dom@5.0.0

Następnie, aby nasz routing działał poprawnie, musimy wprowadzić nieco zmian w pliku webpack.config.js:

  1. W obiekcie output, tuż nad filename: 'scripts_bundle.js',, dodaj: publicPath: '/', – to ustawienie wskaże główne miejsce przechowywania assetów przez naszą aplikację. Potrzebujemy tego ustawienia, ponieważ za chwilę nasza aplikacja będzie działała pod adresami, które domyślnie są traktowane jako podkatalogi projektu.
  2. Pod obiektem output dodaj obiekt:
devServer: {
  historyApiFallback: true,
},

Ustawienie historyApiFallback umożliwi serwowanie pliku index.html, a zarazem naszej aplikacji w sytuacjach, w których serwer zwróciłby błąd 404.

Dodajemy nowe widoki

React Router mamy już jest zainstalowany, ale zanim zobaczymy go w akcji, potrzebujemy mieć w projekcie co najmniej dwa widoki. Jak wspomnieliśmy na początku modułu, będziemy mieć stronę główną (wyświetlającą widok listy kolumn z kartami) i statyczną stronę Info.

W tym celu będziemy musieli zmodyfikować nasz komponent App. Dotychczas to on wyświetlał listę kolumn, teraz posłuży nam tylko do zdefiniowania routingu pomiędzy widokami. Wyświetlaniem treści strony głównej zajmie się nowy komponent, Home.

W folderze components zmień nazwę katalogu App na Home. Następnie zamień App na Home:

  1. w nazwach plików w katalogu Home,
  2. w pliku Home.js:
    • w ścieżce do pliku ze stylami,
    • w nazwie klasy,
    • w eksporcie na końcu pliku,
  3. w pliku HomeContainer.js:
    • w nazwie i ścieżce importu komponentu,
    • w ostatniej linii kodu.

Zrobimy teraz prosty komponent App, aby przetestować, czy Home działa poprawnie. Stwórz katalog src/components/App, a w nim plik App.js. Dodaj do niego poniższy kod:

import React from 'react';
import Home from '../Home/HomeContainer';

const App = () => (
  <div>
    <Home />
  </div>
);

export default App;

Musimy jeszcze przejść do pliku index.js i zmienić ścieżkę importu komponentu App – z ostatniego członu ścieżki tego importu usuń Container (czyli zamień AppContainer na App). Nasz nowy App nie ma kontenera, więc ten import powodowałby błąd.

Teraz strona powinna działać dokładnie tak samo, jak wcześniej – czyli udało nam się zmienić dotychczasowy komponent App na Home. W nowym komponencie App za chwilę zdefiniujemy routing, ale najpierw dodajmy jeszcze drugi widok – Info. W nowym folderze o nazwie src/components/Info stwórz plik Info.js o treści:

import React from 'react';
import Container from '../Container/Container';

const Info = () => (
  <Container>
    <h2>Info</h2>
    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
  </Container>
);

export default Info;

Jesteśmy już gotowi na podłączenie routingu w App! Wykorzystamy do tego trzy komponenty – BrowserRouter, Switch oraz Route. Za chwilę omówimy sobie ich zadania – na razie zaimportujmy je z pakietu react-router-dom. Przy okazji, zaimportujemy też nasz nowy komponent – Info.

import Info from '../Info/Info';
import {BrowserRouter, Switch, Route} from 'react-router-dom';

A cały kod JSX tego komponentu zamienimy na:

<BrowserRouter>
  <Switch>
    <Route exact path='/' component={Home} />
    <Route exact path='/info' component={Info} />
  </Switch>
</BrowserRouter>

W ten sposób plik App.js zamieniliśmy na główne "centrum dowodzenia" routingiem w naszym projekcie. Ten plik informuje aplikację, jakie komponenty powinna wyświetlić, gdy użytkownik znajduje się na określonej podstronie (lub, bardziej precyzyjnie, na określonej ścieżce, czyli path). Strona główna aplikacji będzie pokazywać komponent Home, a gdy na końcu adresu w przeglądarce dodamy /info, przeniesiemy się na naszą nową stronę statyczną.

Zauważ atrybut exact, dzięki któremu dany widok pokaże się tylko wtedy, gdy będziemy na dokładnie takiej ścieżce, jak zdefiniowana w routingu (np. w drugim przypadku musi to być dokładnie /info, a nie np. info/about-me).

Czas sprawdzić, czy nasz routing już działa! Otwórz terminal, w którym działa proces npm start – zatrzymaj go i jeszcze raz uruchom projekt za pomocą komendy npm start. Następnie zmień adres strony w przeglądarce na: http://localhost:8080/info.

Jeżeli wszystko poszło dobrze, ujrzysz treść podstrony Info!

Komponent MainLayout

Mamy już w naszym projekcie dwa osobne widoki, musimy teraz dodać sposób na łatwe przemieszczanie się między nimi. Nawigacja, którą stworzymy, będzie znajdować się na każdej "podstronie", dlatego skorzystamy z dobrej reactowej praktyki i umieścimy ten element w nowym komponencie – MainLayout. Tak naprawdę nazwa tego komponentu mogłaby być dowolna, ale my skorzystamy z często używanego wzorca.

Jeżeli w przyszłości będziesz dodawać do projektu inne elementy, które będą pojawiać się na każdym widoku, również umieścisz je w MainLayout.

Zacznijmy więc od utworzenia katalogu o tej nazwie, a w nim pliku MainLayout.js:

import React from 'react';
import PropTypes from 'prop-types';

const MainLayout = ({children}) => (
  <div>
    {children}
  </div>
);

MainLayout.propTypes = {
  children: PropTypes.node,
};

export default MainLayout;

Następnie zaimportuj ten komponent w komponencie App i zwrappuj cały komponent Switch w komponent MainLayout:

<MainLayout>
  <Switch>
    <Route exact path='/' component={Home} />
    <Route exact path='/info' component={Info} />
  </Switch>
</MainLayout>

Strona powinna nadal wyświetlać się tak samo, jak wcześniej. Na razie nasz MainLayout tylko zagnieżdża całą stronę w divie. Za chwilę jednak będzie też dodawał menu naszej strony.

Dodajemy nawigację

Zgodnie z zasadą modułowości, umieścimy samą nawigację w osobnym komponencie, a później dodamy ten komponent do MainLayout. Stwórz w projekcie nowy komponent, Header, i umieść w jego folderze plik Header.scss z poniższą zawartością:

@import '../../styles/settings.scss';

.component {
  background: darken($color-dark, 3%);
  padding: ($base-size * 1.5) 0;

  a {
    color: $color-light;
    text-decoration: none;
    font-weight: 700;
    margin-right: $base-size;
    transition: $transition;
    &:hover {
      color: $color-success;
    }
    &:global(.active) {
      color: $color-warning;
    }
  }

}

.wrapper {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.logo {
  font-size: 24px;
}

Następnie utwórz plik Header.js – będzie on zawierał komponent klasowy. Spróbuj samodzielnie poradzić sobie z tym zadaniem, wykonując poniższe kroki:

  1. Zaimportuj Reacta.
  2. Zaimportuj komponent {NavLink, Link} z 'react-router-dom'.
  3. Zaimportuj style komponentu Header.
  4. Spraw, by komponent renderował:
  • element <header> z klasą komponentu wziętą z pliku styli;
  • wewnątrz niego – element Container;
  • wewnątrz elementu Container – element <div> z klasą wrapper wziętą z pliku styli;
  • wewnątrz wrappera – komponent Link z klasą logo wziętą z pliku stylów i propsem to ustawionym na '/';
  • wewnątrz komponentu Link – komponent Icon wyświetlający dowolną ikonę z FontAwesome;
  • pod komponentem Link – element <nav>, w środku którego znajdą się dwa linki do naszych widoków:
<NavLink exact to='/'>Home</NavLink>
<NavLink exact to='/info'>Info</NavLink>

Pamiętaj o niezbędnych importach!

import React from 'react';
import {NavLink, Link} from 'react-router-dom';
import styles from './Header.scss';
import Container from '../Container/Container';
import Icon from '../Icon/Icon';

class Header extends React.Component {
  render(){
    return (
      <header className={styles.component}>
        <Container>
          <div className={styles.wrapper}>
            <Link to='/' className={styles.logo}>
              <Icon name='cat' />
            </Link>
            <nav>
              <NavLink exact to='/'>Home</NavLink>
              <NavLink exact to='/info'>Info</NavLink>
            </nav>
          </div>
        </Container>
      </header>
    );
  }
}

export default Header;

Zauważ, w jaki sposób tworzymy linki w nawigacji w aplikacjach reactowych: zamiast HTML-owego tagu <a> stosujemy komponent <NavLink>. Jeżeli natomiast chcemy umieścić zwykły link w innym miejscu na stronie, użyjemy komponentu <Link>. W obu przypadkach zamiast atrybutu href użyjemy to do wskazania naszej aplikacji, gdzie dany link ma prowadzić.

Teraz możesz wykorzystać Header w kodzie JSX komponentu MainLayout, tuż nad {children}. Pamiętaj, by najpierw go zaimportować! To zadanie również spróbuj wykonać samodzielnie.

import React from 'react';
import PropTypes from 'prop-types';
import Header from '../Header/Header';

const MainLayout = ({children}) => (
  <div>
    <Header />
    {children}
  </div>
);

MainLayout.propTypes = {
  children: PropTypes.node,
};

export default MainLayout;

Komponent Header wyświetli nawigację, podczas gdy props {children} będzie renderować wszystko, co zostało dodane w tagach <MainLayout> w App.js.

Aktywne linki

Nawigacja jest już na miejscu i działa! Dodamy do niej ostatni element, czyli podświetlenie aktywnego linka. W React Router jest to banalnie proste: daje nam on możliwość dodawania do linków nawigacji atrybutu activeClassName, czyli nazwy klasy, która zostaje dołączona do linka w przypadku, kiedy dana strona jest aktywna:

<NavLink exact to='/' activeClassName='active'>Home</NavLink>
<NavLink exact to='/info' activeClassName='active'>Info</NavLink>

Dodaj ten atrybut do obu linków w nawigacji i sprawdź, czy style dla aktywnego linka zadziałały.

Zadanie: Rozwinięcie podstron

Tym razem przećwiczymy wykorzystanie różnych elementów zdobytej do tej pory wiedzy – zarówno z zakresu Routera, Reduksa, jak i samego Reacta.

Twoim zadaniem jest dodanie jeszcze jednej podstrony – FAQ. Pamiętaj, aby dodać ją do headera oraz routingu!

Zarówno dla Info, jak i FAQ, dodaj:

  • Container,
  • komponent Hero z tytułem i obrazkiem,
  • przykładową treść.

Następnie zrobimy trochę porządku – rozwiń i wykorzystaj obiekt settings z dataStore.js, aby z niego czerpana była nazwa ikony używanej jako logo w headerze. Następnie w dataStore.js dodaj nowe obiekty z danymi dla podstron Info oraz FAQ – mają zawierać tytuł, adres obrazka oraz treść podstrony. Nie musisz dodawać ich do stanu, wystarczy, że zaimportujesz odpowiednie obiekty z dataStore.js.

Po tych zmianach w komponentach Header, Info i FAQ nie powinno być wpisanych żadnych treści wyświetlanych na stronie – wszystkie będą pobierane z dataStore.js.

Powodzenia!

17.3. Przejścia pomiędzy widokami

Potrafisz już tworzyć za pomocą Reacta aplikacje typu SPA, posiadające wiele widoków – fantastycznie! Dodajmy teraz do naszego projektu to-do ciekawy efekt wizualny, który uprzyjemni korzystanie z aplikacji.

Animowane zmiany widoku

Aby nasza aplikacja wyglądała bardziej profesjonalnie, dorzucimy do niej łagodne animacje przejścia między poszczególnymi widokami. Jest na to kilka sposobów o różnym poziomie trudności, ale najwygodniejszym jest użycie gotowej paczki react-router-transition. Warto zapoznać się z jej dokumentacją, w której znajdziesz gotowe przykłady.

Najpierw zainstaluj paczkę w projekcie (korzystamy z wersji 1.4.0):

npm install -S react-router-transition@1.4.0

Następnie do komponentu App dodaj odpowiedni import:

import {AnimatedSwitch} from 'react-router-transition';

Na samym końcu zmień nazwę komponentu Switch na AnimatedSwitch i dodaj parametry animacji:

<BrowserRouter>
  <MainLayout>
    <AnimatedSwitch
      atEnter={{ opacity: 0 }}
      atLeave={{ opacity: 0 }}
      atActive={{ opacity: 1 }}
      className="switch-wrapper"
    >
      <Route exact path="/" component={Home} />
      <Route exact path="/info" component={Info} />
    </AnimatedSwitch>
  </MainLayout>
</BrowserRouter>

Spróbuj teraz przełączyć się między widokami w projekcie i zauważ, jak zmienił się efekt!

Poprawa płynności animacji

Przełączanie pomiędzy widokami będzie wyglądać jeszcze lepiej, jeśli dodamy App.scss z odrobiną stylów:

.switchWrapper {
  position: relative;

  > :first-child ~ * {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
  }
}

Pamiętaj, aby zaimportować ten plik jako styles w App.js i wykorzystać go w propsie className komponentu AnimatedSwitch:

<AnimatedSwitch
  atEnter={{ opacity: 0 }}
  atLeave={{ opacity: 0 }}
  atActive={{ opacity: 1 }}
  className={styles.switchWrapper}
>

Mimo tych zmian nadal możesz zauważyć przeskakiwanie strony przy przechodzeniu pomiędzy podstronami, jeśli Twoje okno przeglądarki jest niższe niż zawartość strony Home. Wynika to z tego, że na stronie Info jest mniej treści, więc znika pasek przewijania. Dlatego warto w src/styles/global.scss dodać dla body właściwość overflow-y: scroll;. Dzięki niej pasek przewijania będzie zawsze widoczny.

Teraz nie powinno już być żadnych przeskoków, a przechodzenie pomiędzy podstronami powinno być płynne.

17.4. Parametry w adresie strony

Udało nam się już stworzyć strony statyczne i podłączyć routing do nich! Jak widzisz, więcej mieliśmy pracy ze stworzeniem tych podstron i nawigacji, niż z samym routingiem. To jest właśnie zaleta pakietu React Router!

Co więcej, dzięki niemu nasza aplikacja działa również przy odświeżaniu strony i używaniu przeglądarkowych guzików wstecz i naprzód!

Teraz zajmiemy się trochę bardziej skomplikowaną funkcjonalnością – chcemy na stronie głównej mieć tylko linki do poszczególnych list. Będą one kierować pod adresy typu /list/someListId, które będą miały inny ostatni człon dla każdej z list. Pod każdym z tych adresów będzie wyświetlać się pojedyncza lista.

Zaczynamy od stworzenia takiego pojedynczego linka do listy.

Stwórz nowy katalog src/components/ListLink i wklej do niego pliki z paczki ZIP, dostępnej pod poniższym linkiem.

Pobierz komponent `ListLink`

Stworzyliśmy go na podstawie komponentu List, wprowadzając kilka zmian:

  • cały element został wstawiony do komponentu Link, kierującym pod odpowiedni adres, zawierający id listy,
  • zamiast komponentu Hero użyliśmy układu dwóch kolumn – lewej z tytułem i opisem, i prawej ze zdjęciem,
  • ten komponent nie potrzebuje kontenera, ponieważ nie korzysta z reduksowego stanu aplikacji, tylko z propsów przekazanych przez rodzica (komponent Home, w którym za chwilę go wykorzystamy).

Zmiany w komponencie Home

Przejdźmy teraz do Home.js, w którym musimy wprowadzić drobne zmiany. Po pierwsze, zmieniamy import komponentu List na import komponentu ListLink. Pamiętaj, aby zmienić zarówno nazwę importowanego komponentu, jak i ścieżkę do jego pliku.

Drugą zmianą będzie wykorzystanie w kodzie JSX komponentu ListLink zamiast List. Wystarczy, że zmienisz nazwę komponentu – jego propsy mają pozostać bez zmian.

Wreszcie, ostatnią zmianą będzie usunięcie komponentu Search oraz jego importu. Nie będzie nam już potrzebny na tej stronie.

Po tych zmianach nasza strona Home powinna już wyświetlać linki do list, w postaci boksów z tytułem, opisem i zdjęciem. Na razie jednak mamy tylko jedną listę – dodajmy więc kolejne.

Dodanie list w danych źródłowych

Przejdź do pliku src/data/dataStore.js i dodaj kolejne dwie listy. Pamiętaj, że muszą mieć unikalne id – tytuł, opis i obrazek możesz zmienić na dowolne, jakie chcesz.

Do testowania zmian w aplikacji nie musisz dodawać kolejnych kolumn czy kart, chyba że masz na to ochotę.

Routing dla list

W komponencie App.js musimy teraz dodać kolejny komponent Route, który będzie obsługiwał adresy wyświetlające pojedynczą listę. Wystarczy, że dodasz go tam, gdzie pozostałe Route w kodzie JSX:

<Route exact path="/list/:id" component={List} />

Nowością jest tutaj użycie :id w ścieżce strony. Taki zapis sprawi, że nasz komponent List otrzyma informację o tym, co znalazło się w adresie strony po /list/, czyli w miejscu, w które wpisaliśmy :id. Jak możesz się domyślać, id nie jest jakimś szczególnym słowem – to nasz wybór, że pod takim kluczem chcemy mieć dostępną tę informację.

Nie zapomnij dodać też importu List z pliku jego kontenera, czyli:

import List from '../List/ListContainer';

To już koniec zmian w App.js!

Zmiany w ListContainer

Niestety, jeśli teraz przetestujesz stronę i klikniesz jeden z linków do list na stronie głównej, zobaczysz, że lista nie wyświetla się poprawnie. Wynika to z tego, że do tej pory była inaczej wykorzystywana:

  • propsy takie jak title czy description były do niej przekazywane z rodzica (komponentu Home), więc musimy sprawić, by były pobierane ze stanu aplikacji,
  • id listy było dla niej dostępne jako props.id, a teraz będzie dostępne jako props.match.params.id.

Ta ostatnia różnica wynika z tego, że props.match zawiera informacje przekazywane przez Route, zawierające m.in. adres strony i parametry w nim zawarte – takie jak :id.

Przejdźmy więc do pliku ListContainer.js, aby wykonać ostatnie zmiany!

W tej chwili funkcja mapStateToProps powinna wyglądać tak:

const mapStateToProps = (state, props) => ({
  columns: getColumnsForList(state, props.id),
});

Jest to funkcja, która przyjmuje dwa argumenty (state i props), a zwraca obiekt zawierający jedną właściwość (columns). My jednak chcemy, aby ten obiekt zawierał również wszystkie inne właściwości tej listy, takie jak np. title. Aby je wydostać ze stanu aplikacji, musimy przefiltrować tablicę lists, wybrać listę o tym samym id, a następnie wykorzystać jej właściwości.

Będzie nam dużo wygodniej to zrobić, zmieniając tę funkcję strzałkową – dodamy blok { }, który pozwoli nam na wykonanie większej liczby operacji. Zacznijmy więc od wykonania tylko tej zmiany.

const mapStateToProps = (state, props) => {
  return {
    columns: getColumnsForList(state, props.id),
  };
};

Dodajmy sobie dla wygody stałą id, i wykorzystajmy ją od razu jako atrybut funkcji getColumnsForList:

const mapStateToProps = (state, props) => {
  const id = props.match.params.id;

  return {
    columns: getColumnsForList(state, id),
  };
};

Będziemy też potrzebować obiektu z właściwościami tej listy – w tym celu przefiltrujemy stan aplikacji.

const mapStateToProps = (state, props) => {
  const id = props.match.params.id;
  const filteredLists = state.lists.filter(list => list.id == id);

  return {
    columns: getColumnsForList(state, id),
  };
};

Nasza przefiltrowana lista powinna zawierać jeden element – jest to obiekt zawierający właściwości listy. Możemy więc zapisać go w nowej stałej listParams. Na wypadek, gdyby w stanie aplikacji nie było listy o id z adresu strony, dodamy || {}, czyli "lub użyj pustej tablicy", aby nie spotkać się z błędem.

const mapStateToProps = (state, props) => {
  const id = props.match.params.id;
  const filteredLists = state.lists.filter(list => list.id == id);
  const listParams = filteredLists[0] || {};

  return {
    ...listParams,
    columns: getColumnsForList(state, id),
  };
};

Rozpakowaliśmy zawartość listParams do zwracanego obiektu, aby właściwości takie jak title czy description zostały przekazane do komponentu List jako propsy.

Pozostaje nam jeszcze jedna zmiana do wykonania – w funkcji mapDispatchToProps musimy zmienić props.id na props.match.params.id, aby poprawnie działało dodawanie nowych kolumn!

Po tych zmianach aplikacja powinna działać bez zarzutu! Po kliknięciu w link na stronie głównej powinna pokazać nam się jedna z list. Powrót na stronę główną jest możliwy po kliknięciu w link Home lub w logo w nagłówku strony. Możemy też użyć przeglądarkowego guzika wstecz!

Zwróć uwagę, że po dodaniu karty (lub kolumny) do którejś z list, możesz przechodzić na inne podstrony aplikacji – po powrocie na tę samą listę dodana karta (lub kolumna) będzie nadal widoczna! Oznacza to, że nasza aplikacja cały czas działa i pamięta stan aplikacji, mimo przechodzenia na inne widoki!

Zadanie: Wyniki wyszukiwania

Mamy już działającą aplikację z routingiem – teraz czas na zadanie podsumowujące zdobytą do tej pory wiedzę!

W trakcie implementacji routingu usunęliśmy komponent Search. Musimy teraz ponownie go wykorzystać, ale tym razem będzie on działał zupełnie inaczej! Nie będzie już filtrował kart wyświetlanych w kolumnach listy, tylko pokaże zupełnie nowy widok wyników wyszukiwania!

Dodaj Search do komponentu Header, aby był zawsze widoczny. Następnie wprowadź trzy zmiany w pliku Search.js:

  1. dodaj import import {withRouter} from "react-router";,
  2. zamień eksport na końcu pliku na export default withRouter(Search),
  3. do jego metody handleOK dodaj:
this.props.history.push(`/search/${this.state.value}`);

Dzięki tym zmianom powyższe wyrażenie będzie zmieniać adres strony, informując o tym Router. Teraz, po kliknięciu guzika OK, powinien zmienić się adres strony – będzie zawierał /search/ oraz to, co zostało wpisane w polu wyszukiwania.

Z dalszymi krokami poradzisz sobie już samodzielnie:

  • w cardsRedux.js musisz usunąć fragment kodu, odpowiadający za filtrowanie kart wedle wyszukanej frazy,
  • musisz dodać Route w App.js do obsługi adresów /search/...,
  • będziesz też potrzebować nowego komponentu – SearchResults, który stworzysz wzorując się na Column,
  • w cardsRedux.js będzie potrzebny nowy selektor, wybierający karty ze wszystkich list, z uwzględnieniem wyszukiwanej frazy.

I wreszcie, najtrudniejsza część zadania – to wszystko ma działać również wtedy, kiedy wyszukasz jakąś frazę, a następnie odświeżysz stronę! W tym celu proponujemy, aby SearchResults posiadał kontener, podobnie jak Search. Wtedy w momencie renderowania tego komponentu możesz zapisać do reduksowego stanu aplikacji wyszukiwaną frazę.

Na tym etapie z metody handleOk w Search.js możesz usunąć this.props.changeSearchString(this.state.value);, ponieważ kliknięcie OK po wyszukaniu ma już tylko zmieniać adres strony – zmianą stanu zajmie się komponent SearchResults.

Wymagania

Podsumujmy wymagania tego zadania:

  • w nagłówku strony ma znajdować się pole wyszukiwania,
  • po wyszukaniu jakiejkolwiek frazy ma zostać wyświetlony widok wyników wyszukiwania, z adresem strony zawierającym wyszukiwaną frazę,
  • widok wyników ma wyświetlać wszystkie karty (ze wszystkich list i kolumn) pasujące do wyszukiwanej frazy,
  • bezpośrednio po przejściu z wyników wyszukiwania do którejkolwiek z list, mają być widoczne wszystkie karty na tej liście (nie tylko pasujące do wyszukiwanej frazy, ponieważ wyszukiwanie ma mieć wpływ tylko na widok wyników wyszukiwania),
  • wyszukiwanie ma opierać się o frazę z adresu strony – czyli po wyszukaniu jakiejkolwiek frazy i odświeżeniu strony, wyszukiwana fraza ma znajdować się w polu wyszukiwania i mają być widoczne te same wyniki wyszukiwania, które były widoczne przed odświeżeniem.

Dla chętnych

Jeśli czujesz się na siłach, możesz rozwinąć to zadanie o następujące funkcjonalności:

  • w wynikach wyszukiwania, karty mogłyby zawierać informację o liście i kolumnie, w której się znajdują – najlepiej, gdyby był to link do widoku tej listy,
  • w widoku listy, karty pasujące do ostatnio wyszukiwanej frazy mogłyby mieć inny kolor tła, aby były łatwe do odnalezienia,
  • wyniki wyszukiwania mogłyby też zawierać linki do list, których nazwa pasuje do wyszukiwanej frazy – w rozszerzonej wersji tej funkcjonalności można uwzględnić również listy, które zawierają kolumnę, o nazwie pasującej do wyszukiwanej frazy.

17.5. Nowy projekt – agencja turystyczna

Jak z pewnością udało Ci się już zauważyć, routing jest zagadnieniem dużo prostszym od Reduksa. Właśnie dlatego więcej czasu zajęło nam przygotowanie odpowiednich widoków niż sama konfiguracja routingu.

Mamy nadzieję, że na tym etapie tworzenie komponentów nie jest już dla Ciebie czarną magią – dlatego skupimy się na trenowaniu umiejętności związanych z routingiem i Reduksem. W tym celu zaczniemy nowy projekt! A właściwie, będziemy go kontynuować...

Za chwilę pobierzesz projekt agencji turystycznej, który jest w zaawansowanym stadium rozwoju. Twoim zadaniem będzie dokończenie go, a konkretniej:

  • w projekcie brakuje routingu dla niektórych widoków,
  • nie działa filtrowanie wycieczek.

Szczegółowy opis zadania znajdziesz poniżej – najpierw jednak musimy zapoznać się z projektem.

Uruchomienie projektu

Zacznij od założenia na GitHubie nowego repozytorium – travel-agency. Następnie sklonuj je i rozpakuj do niego zawartość paczki, dostępnej pod poniższym linkiem.

Pobierz pliki projektu

Następnie zainstaluj pakiety za pomocą komendy npm install i uruchom projekt – npm start. Twoim oczom powinna ukazać się strona główna naszej agencji turystycznej!

Początkowy stan projektu

Szybko możesz zorientować się, że nasz projekt wymaga dokończenia – działa w nim tylko strona główna, lista wycieczek oraz strona kontaktowa. W nagłówku strony możesz też znaleźć linki Countries oraz Regions, ale one nie działają. Na liście wycieczek można kliknąć w każdą z nich, co również przeniesie nas na stronę 404 (czyli "nie znaleziono takiej strony"). Możesz się też domyślać, że na liście krajów z niedziałającej strony Countries będą dostępne linki do krajów – które też nie będą działać.

Mamy jednak dla Ciebie dobrą wiadomość – w plikach projektu znajdziesz wszystkie te niedziałające widoki! Oznacza to, że wystarczy skonfigurować poprawny routing, aby strona ożyła!

Nieco większym wyzwaniem będzie usprawnienie filtrów na liście wycieczek. Całe szczęście działa chociaż jeden z filtrów – wyszukiwanie. Będziemy mogli się na nim wzorować przy dodawaniu funkcjonalności pozostałych filtrów.

Analiza plików projektu

Ten projekt pod wieloma aspektami jest podobny do aplikacji to-do, którą rozwijaliśmy do tej pory. Znajdziesz w nim jednak kilka istotnych różnic. Omówimy je sobie pokrótce, aby łatwiej było Ci zrozumieć działanie projektu.

Konfiguracja projektu

Wszystko poza katalogiem src jest prawie identyczne, jak w projekcie to-do. Oczywiście najbardziej kluczowym plikiem jest package.json, w którym zmieniły się tylko dodatkowe pakiety. Dodaliśmy react-flexbox-grid (który za chwilę omówimy), a usunęliśmy niewykorzystywane w tym projekcie shortid (oraz react-beautiful-dnd z submodułu dla chętnych w poprzednim module).

Nadal używamy ESLinta oraz Husky'ego, który lintuje nasze pliki przy zapisywaniu commita. Pamiętaj, aby czytać komunikaty w terminalu po każdej próbie zapisu commita – jeśli ESLint wykryje błąd, commit nie zostanie zapisany!

W konfiguracji webpacka również mamy tylko kosmetyczną zmianę – uwzględniliśmy flexboxgrid z pakietu react-flexbox-grid.

Poza tymi zmianami wszystko powinno wyglądać znajomo.

Struktura katalogu src

W katalogu źródłowym już na pierwszy rzut oka zauważysz dwie nowości! Pierwszą z nich jest katalog utils, który stworzyliśmy na potrzeby wydzielenia nieco rozbudowanej funkcji, wykorzystywanej w komponencie App. Druga zmiana dotyczy właśnie tego komponentu – znalazł się bezpośrednio w katalogu src!

Zmieniliśmy lokalizację tego komponentu, ponieważ jego rola istotnie różni się od pozostałych komponentów. Służy nam przede wszystkim do konfiguracji routingu, a nie jest odpowiedzialny za żadne elementy widoczne na stronie. Skoro jest plikiem zarządzającym aplikacją, to wylądował na równi z index.js.

Kolejną nowość znajdziesz w katalogu src/components – znajdują się w nim cztery katalogi, grupujące komponenty. W poprzednim projekcie wszystkie komponenty znajdowały się bezpośrednio w components, ale jest to podejście mało odporne na rozwój projektu. Nawet w aplikacji to-do mogło być dla Ciebie irytujące znalezienie plików konkretnego komponentu. Tym razem będzie trochę większy porządek, ponieważ podzieliliśmy je na cztery kategorie:

  1. layout – to komponenty związane z układem elementów na stronie oraz elementy widniejące na każdej stronie (Header),
  2. views – w tym katalogu znajdziesz komponenty wyświetlane za pomocą routingu, czyli podstrony naszej aplikacji,
  3. features – fragmenty widoków znajdujących się w views, czyli np. pojedynczy box z listy wycieczek,
  4. common – podstawowe komponenty wykorzystywane do różnych celów, takie jak np. guziki, ikony czy wrappery obrazków.

Pamiętaj, że ten podział nie jest jedynym poprawnym podejściem. Dlatego przy tworzeniu nowego komponentu, nigdy nie poświęcaj więcej niż 2-3 minut na decyzję, gdzie go umieścić (a zarazem katalogu w components). W razie wątpliwości możesz kierować się rozmiarem komponentu: views składają się z features, które wykorzystują common – a layout to układ strony i jego stałe elementy.

W tych czterech katalogach znajdziesz foldery komponentów, których zawartość znasz już z poprzedniego projektu. W każdym z nich znajduje się plik komponentu oraz mogą się w nim znaleźć (jeśli są używane) style i kontener.

Uczulamy na to, aby nie przywiązywać się do tego podziału, ponieważ jest to kwestia preferencji programisty czy zespołu projektowego. Nie ma co do tego żadnych znaczących wytycznych – poza jednym: należy wybrać podział wedle funkcjonalności lub rodzaju plików.

My od początku stosujemy podział funkcjonalny, czyli np. widok Trips posiada swój katalog, w którym znajdziesz też jego kontener i style. Dotyczy to również Reduksa – selektory, typy akcji i ich selektory, oraz reducer, znajdują się w jednym pliku dla danego fragmentu stanu aplikacji (np. tripsRedux.js).

Drugim podejściem, które obecnie jest coraz rzadziej wykorzystywane, jest podział ze względu na rodzaj plików – w tym podejściu znajdziesz osobne katalogi na komponenty, kontenery, style, akcje, reducery i selektory. Przez większość społeczności reactowej, to podejście uznaje się za mniej logiczne i utrudniające rozwój projektu – ale wspominamy o nim, ponieważ możesz się z nim spotkać, dołączając do projektu w swojej pierwszej pracy.

Katalog src/data

Tym razem dane źródłowe znajdują się w plikach .json – zobaczysz jednak (np. w store.js), że ich wykorzystanie nie sprawi nam żadnego problemu.

W tym samym katalogu znajdziesz również plik tripGenerator.js, który nie jest używany w projekcie. Jest to plik zawierający konfigurację, wykorzystywaną w generatorze plików JSON. Użyliśmy jej do wygenerowania pliku trips.json, czyli wszystkich wycieczek dostępnych na stronie naszej agencji turystycznej.

Zawarliśmy ten plik na wypadek, gdyby miał Ci się przydać do zmiany produktów oferowanych na stronie. Przy okazji, chcieliśmy pokazać Ci, w jaki sposób można tworzyć przykładowe dane do aplikacji. Dzięki temu w naszej aplikacji nie mamy wycieczek o nazwach "Trip 1", "Trip 2" itd.

Nie przejmuj się złożonością pliku tripGenerator.jsw przykładowej konfiguracji możesz zobaczyć, że konfigurację można napisać w bardzo prosty sposób. Rozbudowaliśmy ją jednak, aby np. cena wycieczki była proporcjonalna do jej długości (liczby dni). W tym pliku znajdziesz też pełną listę krajów, dostępnych w countries.json – większość z nich jest zakomentowana, aby w losowym zestawie danych było po kilka wycieczek do tego samego kraju. Możesz jednak dowolnie zmieniać tę konfigurację.

Redux

Reduksa używamy bardzo podobnie do poprzedniego projektu, z jedną istotną różnicą. W store.js możesz zobaczyć, że jako storeReducer używamy własnej funkcji, która uruchamia globalReducer, a dopiero potem połączone reducery cząstkowe.

Dzięki temu mogliśmy w pliku globalRedux.js zdefiniować reducer, który obsługuje cały stan aplikacji, a nie tylko jego fragment. Potrzebujemy tego do jednoczesnego ustawienia kilku właściwości znajdujących się w stanie aplikacji.

Konkretniej rzecz biorąc, chodzi o funkcję parseTrips.js, którą znajdziesz w katalogu src/utils. Wykorzystujemy ją w komponencie App. Przyjmuje ona listę wszystkich wycieczek, a szczegółowe informacje o krajach pobiera z pliku countries.json, ale w stanie nie są zapisywane dane wszystkich krajów – tworzy zestawienie wyłącznie tych krajów, regionów i subregionów, do których oferujemy wycieczki.

W pliku countries.json każdy kraj posiada przypisany region i subregion – dlatego tworzymy też ich zestawienie, aby móc wyświetlać kraje (lub wycieczki) wedle tego podziału.

Poza globalnym reducerem reszta reduksowego magazynu działa tak jak wcześniej. W poszczególnych plikach ...Redux.js znajdziesz selektory, typy akcji z ich kreatorami, oraz reducery. Nie zdziw się, że w niektórych z nich została zakomentowana cała część dotycząca akcji i reducera – jeśli dla danego typu danych nie używamy akcji, zostawiliśmy zakomentowaną strukturę, która ułatwi Ci w przyszłości rozwój projektu.

Layout strony

Jak wspomnieliśmy wcześniej, w tym projekcie używamy pakietu react-flexbox-grid, który dostarcza nam komponentów:

  • Grid – stosujemy go zamiast Container i nie zagnieżdżamy kolejnego Grid w nim,
  • Row – czyli wiersz, w którym umieszczamy kolumny,
  • Col – to są kolumny, którym możemy nadawać szerokości dla różnych rozdzielczości.

Przykłady jego zastosowania znajdziesz na stronie tego pakietu oraz w dokumentacji na GitHubie.

Wykorzystaliśmy tę paczkę, mimo jej ubogiej dokumentacji, ponieważ dostarcza nam wszystkie funkcjonalności, jakie były nam potrzebne w tym projekcie. Pozwala na wykorzystanie grida, bardzo podobnego do bootstrapowego, bez konieczności załączania całego bootstrapa.

Dzięki temu nie musieliśmy samodzielnie tworzyć komponentów odpowiedzialnych za układ kolumnowy, co jest znacznym ułatwieniem przy tworzeniu projektu.

Uzupełnieniem tego pakietu jest komponent Section, który znajdziesz w src/layout, pozwalający nam na stosowanie różnych wariantów ostylowanej sekcji strony.

Szybkie wdrożenie w projekt

Powyższe informacje powinny być wystarczające do tego, aby wykonać zadanie i dokończyć ten projekt. Jest to jednak tylko pobieżny przegląd jego struktury – głębszą wiedzę na jego temat zdobędziesz, analizując jego działanie w trakcie pracy nad nim.

Wiemy, że takie wskoczenie na głębokie wody projektu może być dla Ciebie frustrujące – ale właśnie dlatego to zadanie znalazło się w tym module. Jesteś już na tym etapie kursu, na którym musisz myśleć o swojej przyszłej pracy jako Junior Web Developer.

Oczywiście, byłoby idealnie, gdyby dołączenie do projektu wiązało się z paroma tygodniami wdrożenia. Może jednak zdarzyć się inaczej – czasami programista musi być gotowy do wskoczenia do pędzącego projektu, którego termin już minął, a prawnik klienta wysyła pozdrowienia do działu obsługi.

W takiej sytuacji możesz nie mieć czasu na spokojne zapoznanie się z projektem. Musisz spróbować poradzić sobie z postawionym przed Tobą zadaniem, wdrażając się w projekt tylko na tyle, na ile wymaga tego rozwiązanie problemu.

Dlatego wykorzystaj tę okazję do sprawdzenia się w takiej sytuacji. Wiesz, że jest to projekt reactowy, o strukturze bardzo podobnej do wcześniejszej aplikacji. Mało tego, wiesz co dokładnie jest do zrobienia (opis poniżej), więc spróbuj nie przejmować się tym, że nie znasz każdego zakamarka projektu.

Zadanie: Implementacja routingu i Reduksa

Jak wcześniej wspomnieliśmy, do wykonania są dwa obszary, którymi należy się zająć:

  1. uruchomienie niedziałających widoków,
  2. filtrowanie wycieczek na stronie Trips.

Omówmy sobie każdy z tych obszarów!

Niedziałające widoki

Możemy podzielić niedziałające widoki na dwie kategorie. Pierwszą z nich są widoki, do których linki znajdziesz w nagłówku strony. Dla nich wystarczy skonfigurować routing, wiążąc te adresy z komponentami z katalogu src/components/views.

Drugą kategorią są linki w widokach Trips i Countries – znajdziesz tam boksy z wycieczkami i krajami, które linkują do stron przedstawiających szczegółowe informacje o danej wycieczce lub danym kraju. One również będą potrzebowały routingu ze wskazaniem właściwych komponentów z src/components/views, ale w ścieżce tych routingów musi znaleźć się parametr id.

Na wykonaniu tych kroków jednak nie skończy się Twoja praca – szybko zauważysz, że coś nie działa prawidłowo. Zarówno na stronie pojedynczej wycieczki, jak i liście wycieczek na stronie kraju, nie wyświetlają się właściwe wycieczki! Wynika to z tego, że niepoprawnie działają selektory getTripById i getTripsForCountry z pliku tripsRedux.js. Musisz je uzupełnić, aby zwracały poprawnie przefiltrowane dane.

Ostatnim krokiem pracy nad usprawnieniem widoków jest implementacja animowanych przejść pomiędzy widokami. Klient chciałby, aby przy przejściu z widoku A na widok B:

  • widok A zmieniał swoje opacity do zera,
  • widok B zmieniał swoje opacity do jedynki, ale ma zacząć pojawiać się o 200px za nisko, i płynnie "wjechać" na poprawną pozycję.

Filtrowanie listy wycieczek

W pliku TripListOptions.js, który znajdziesz w src/components/features, umieszczony został kod odpowiedzialny za filtry listy wycieczek. Możesz w nim zobaczyć, że formularze mają już nawet podłączone metody do eventu change, za pomocą propsa onChange. Na razie jednak te metody (poza wyszukiwaniem) jedynie wyświetlają komunikat w konsoli.

Twoim zadaniem jest:

  • stworzenie nowych akcji (typów i kreatorów) w pliku src/redux/filtersRedux.js,
  • wykorzystanie kreatorów tych akcji w TripListOptionsContainer.js, do powiązania dispatcherów z propsami komponentu,
  • użycie tych dispatcherów, zapisanych w propsach, w metodach komponentu TripListOptions,
  • następnie uzupełnienie reducera w filtersRedux.js, aby w reakcji na te akcje odpowiednio zmieniał stan aplikacji.

Nie zapomnij też zajrzeć do store.js, gdzie znajdziesz strukturę stanu aplikacji, a w szczególności jego właściwości filters.

Wskazówki

Na koniec, mamy dla Ciebie kilka wskazówek, które pomogą Ci w realizacji zadania.

  1. W kodzie projektu oznaczyliśmy komentarzami wszystkie miejsca, które wymagają wprowadzenia zmian. Dzięki temu powinno Ci być nieco łatwiej odnaleźć się w realizacji tego zadania.
  2. Filtry nad listą wycieczek (poza wyszukiwaniem) nie pozwalają na zmianę wartości pól czy zaznaczenie checkboksa, ponieważ ich stan zależy od stanu aplikacji – a konkretniej, zapisanych w nim filtrów. Nie przejmuj się, że nie możesz zmieniać zawartości tych pól ani zaznaczać checkboksów. Kiedy akcje zaczną działać i zmieniać stan aplikacji, zawartość tych pól również będzie się zmieniać.
  3. Koniecznie używaj narzędzi developerskich – w szczególności zakładki Redux! Dzięki temu będziesz widzieć dispatchowane akcje, nawet jeśli reducer jeszcze nie zmienia stanu. Będziesz też widzieć, co dokładnie jest zmieniane w stanie aplikacji przed reducer!

I przede wszystkim – nie poddawaj się!

Powodzenia!

17.6. Quiz powtórkowy

Na koniec tego modułu przygotowaliśmy dla Ciebie quiz powtórkowy. Pomoże Ci on powtórzyć wiedzę z poprzednich modułów.

Odpowiedzi tego quizu nie są nigdzie zapisywane, więc są tylko do Twojej wiadomości. Ten quiz ma Ci posłużyć jako pomoc w nauce – dlatego pod każdym pytaniem znajdziesz guzik, który sprawdzi poprawność Twoich odpowiedzi oraz poda Ci wyjaśnienie zagadnienia poruszanego w tym pytaniu.

1. Czym jest reduksowy stan aplikacji?

Wyjaśnienie

Reduksowy stan aplikacji, jak sama nazwa wskazuje, jest stanem aplikacji i nie ma niczego wspólnego ze stanami komponentów. Jest to obiekt, zawierający dane (w tym tablice i obiekty, dla lepszego uporządkowania danych), z których mogą korzystać wszystkie kontenery.

2. Jeśli chcemy, aby komponent List mógł korzystać z informacji zawartych w reduksowym stanie aplikacji, musimy:

Wyjaśnienie

Sam komponent ma zajmować się wyświetlaniem (i ew. funkcjonalnością) elementu strony, w oparciu o otrzymane propsy. Komponent nie wie, czy dany props pochodzi od komponentu nadrzędnego, czy ze stanu aplikacji – i ta wiedza nie jest mu do niczego potrzebna.

Pośrednikiem pomiędzy stanem aplikacji a komponentem jest kontener tego komponentu. Spójrz na przykład zawartości pliku ListContainer.js, który jest kontenerem komponentu List:

import {connect} from 'react-redux';
import List from './List';

const mapStateToProps = state => ({
  columns: state.columns,
});

export default connect(mapStateToProps)(List);

Ten kontener przekaże komponentowi List props columns, w którym znajdzie się zawartość właściwości state.columns, gdzie state to stan aplikacji. Dzięki zastosowaniu funkcji connect na końcu pliku, komponent List otrzyma nową wartość propsa za każdym razem, kiedy w stanie aplikacji zmieni się jakaś kolumna.

Pamiętaj, że oprócz stworzenia kontenera musimy też go użyć – importując komponent List jakimkolwiek innym komponencie (np. w App), musimy zaimportować go z pliku kontenera ListContainer.js.

3. Czym jest reduksowa akcja?

Wyjaśnienie

Akcja jest – jak napisaliśmy w jednej z poprawnych odpowiedzi – prośbą o zmianę stanu aplikacji. Łatwo jednak pomylić ją np. z kreatorem lub dispatcherem akcji, więc przypomnijmy, czym się różnią.

Akcja to obiekt, który musi posiadać właściwość type, i zostanie przekazany do reducerów. Spójrz na te przykłady akcji:

{
  type: 'START_LOADING',
}
{
  type: 'ADD_COLUMN'
  payload: {
    title: 'Zakupy',
  },
}
{
  type: 'DELETE_CARD'
  payload: {
    id: 'card-123',
  },
}

Zwróć uwagę, że właściwość payload jest opcjonalna. Co więcej, moglibyśmy zamiast niej dodać do akcji np. id czy title, ale dla ujednolicenia struktury akcji, wszelkie informacje załączone do akcji umieszczamy w jej właściwości payload.

Dla uniknięcia błędów i lepszej organizacji kodu stosujemy kilka rozwiązań:

  • typ akcji piszemy wielkimi literami,
  • stosujemy funkcję do tworzenia nazw (typów) akcji, aby nie martwić się powielaniem typów akcji w różnych plikach ...Redux.js,
  • nazwy akcji zapisujemy w zmiennych, których nazwy są takie same jak typ akcji,
  • tworzymy funkcje pełniące rolę kreatora akcji, dzięki czemu w kontenerze nie musimy się martwić o poprawną strukturę obiektu akcji.

Te rozwiązania są umieszczone w plikach ...Redux.js, a wykorzystywane w tym samym pliku oraz w kontenerach. Przejdźmy zatem do tego, co znajdziemy w kontenerze.

Kontener, jak już wiesz, jest pośrednikiem pomiędzy Reduksem a komponentem. Przekazuje on komponentowi propsy, w których znajdują się:

  • informacje ze stanu, powiązane z propsami w mapStateToProps,
  • funkcje pełniące rolę dispatcherów akcji, powiązane z propsami w mapDispatchToProps.

Dispatcher akcji, to funkcja, która wyśle obiekt akcji do store'a, aby został przekazany do reducerów. W dispatcherach zwykle wykorzystujemy kreatory akcji, o których wspomnieliśmy powyżej.

;